package com.amkai.aes.webservice.binary.objecthandler.inventory;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.amkai.aes.aescommon.service.binary.job.inventory.OrderByItem;
import com.amkai.aes.aescommon.service.binary.job.inventory.SimpleItemJobParams;
import com.amkai.aes.generatedbeans.organization.Department;

public class SimpleItemQueryBuilder {
		private static final Log logger = LogFactory.getLog(SimpleItemQueryBuilder.class);
		
		private SimpleItemJobParams params;
		private Department department;
		
		private List<Object> paramValues = new ArrayList<Object>();
		private HQLWhereNode whereNode = null;
		private List<String> fetchFields = new ArrayList<String>();
		private int instanceCount;
		private List<PropertyHQLGenerator> fieldGenerators = new ArrayList<PropertyHQLGenerator>();
		private ResolverServiceManager resolverMgr;
		private static final String instance = "z";
		
		private static final String IN_DEPARTMENT_SUBQUERY = "z.baseOrganization.id = ?";
		
		private static final String IN_ACTIVE_SUBQUERY = "z.active = ?";

		public SimpleItemQueryBuilder(SimpleItemJobParams searchParams,
				Department moduleDepartment, ResolverServiceManager resolverMgr) {
			this.params = searchParams;
			this.department = moduleDepartment;
			this.resolverMgr = resolverMgr;
		}

		public String getAllItemQuery() {
			buildQueryStructure(false);
			StringBuilder query = initializeQuery("select " + getQueryFields(instance));
			addFieldFetchStatements(query);
			addWhereConditions(query);
			addFreehandFilter(query);
			addOrderByStatements(query);
			return query.toString();
		}

		public String getSingleItemQuery() {
			buildQueryStructure(true);
			StringBuilder query = initializeQuery(null);
			addFetchStatements(query);
			addIdCondition(query);
			
			return query.toString();
		}
		
		public int getQueryParameterCount() {
			return paramValues.size();
		}
		
		public Object getQueryParameterAt(int index) {
			return paramValues.get(index);
		}
		
		public List<PropertyHQLGenerator> getFieldGenerators() {
			return Collections.unmodifiableList(fieldGenerators);
		}
		
		private String getQueryFields(String instance) {
			StringBuilder sb = new StringBuilder(instance);
			sb.append(".id");
			fieldGenerators.add(new LongHQLGenerator(instance, "id", resolverMgr));
			if (params.getColumnProps() == null) return sb.toString();
			for (String propertyName : params.getColumnProps()) {
				PropertyHQLGenerator generator = PropertyHQLGeneratorFactory.getGenerator(params.getItemClass(), instance, propertyName, resolverMgr, false, false);
				if (generator == null) continue;
				String hqlPropertyName = generator.getHQLPropertyName();
				if (hqlPropertyName == null) continue;
				sb.append(", ");
				sb.append(hqlPropertyName);
				fieldGenerators.add(generator);
			}
			return sb.toString();
		}

		private StringBuilder initializeQuery(String preamble) {
			StringBuilder query = new StringBuilder();
			if (preamble != null) {
				query.append(preamble);
			}
			query.append(" from ");
			query.append(getObjectName());
			query.append(" as ");
			query.append(instance);
			return query;
		}

		private void addWhereConditions(StringBuilder query) {
			if (whereNode == null || whereNode.isEmpty()) return;
			
			query.append(" where (");//veghar AES-7412
			whereNode.walk(query, paramValues);
			query.append(")"); //veghar AES-7412
		}
		private void addFreehandFilter(StringBuilder query){
			query = query.insert(query.length()-1, " and "+instance+".id not in(Select it.id from FreeHandItem it where it.visibleAsSupply = false)"); //veghar AES-7412
		}
		
		private void addIdCondition(StringBuilder query) {
			if (params.getId() == null) return;
			query.append(" where ");
			query.append(instance);
			query.append(".id = ?");
			paramValues.add(params.getId());
		}

		private void addOrderByStatements(StringBuilder query) {
			if (params.getOrderByItems() == null
					|| params.getOrderByItems().isEmpty()) {
				return;
			}
			
			query.append(" order by ");
			boolean first = true;
			for (OrderByItem orderByItem : params.getOrderByItems()) {
				if (!first) {
					query.append(", ");
				}
				first = false;
				query.append(orderByItem.toString(instance));
			}
		}

		private void addFetchStatements(StringBuilder query) {
			for (String fetchItem : fetchFields) {
				query.append(" ");
				query.append(fetchItem);
			}
		}

		private void addFieldFetchStatements(StringBuilder query) {
			for (PropertyHQLGenerator generator : fieldGenerators) {
				String fetchFragment = generator.getAdditionalFetchHQL();
				if (fetchFragment == null || fetchFragment.isEmpty()) continue;
				query.append(" ");
				query.append(fetchFragment);
			}
		}
		
		private void buildQueryStructure(boolean addAllItemsToFetch) {
			whereNode = HQLWhereNodeFactory.createAndNode();
			paramValues.clear();
			fetchFields.clear();
			instanceCount = 0;
			fieldGenerators.clear();
			
			for (String propertyName : params.getColumnProps()) {
				String[] propPathItems = propertyName.split("\\.");
				if (propPathItems.length <= (addAllItemsToFetch ? 0 : 1)) continue;
				extractFieldsToFetch(propPathItems, addAllItemsToFetch ? 0 : 1);
			}
			
			buildWhereDepartment();
			
			buildWhereSearchText();
			
			buildWhereTypeSelect();
		}

		private void extractFieldsToFetch(String[] propPathItems, int pos) {
			String instancePart = instance; 
			for (int i=0; i<propPathItems.length-pos; i++) {
				String nextInstance = generateInstanceName(propPathItems[i]);
				String formatStr = "left join fetch %s.%s" + (i < propPathItems.length-1-pos ? " %s" : "");
				fetchFields.add(String.format(formatStr, instancePart, propPathItems[i], nextInstance));
				instancePart = nextInstance;
			}
		}
		
		private String generateInstanceName(String propertyName) {
			return String.format("%s_%02d", propertyName, instanceCount++);
		}
		
		private void buildWhereTypeSelect() {
			if (!hasFilter()) return;

			PropertyHQLGenerator generator = PropertyHQLGeneratorFactory
					.getGenerator(params.getItemClass(),
							instance,
							params.getFilterPropertyName(),
							resolverMgr,
							true,
							true);
			if (generator == null) {
				logger.debug(String.format("Could not retrieve a PropertyHQLGenerator " +
						"for the item kind: %s and the filter property: %s",
						params.getItemClass(),
						params.getFilterPropertyName()));
				return;
			}
			
			whereNode.addChild(HQLWhereNodeFactory.createParametrizedConditionNode(
					generator.getHQLFragment(),
					generator.getValueFromText(params.getFilterPropertyValue())));
		}

		private void buildWhereSearchText() {
			if (!hasSearchText()) return;
			
			HQLWhereNode orNode = HQLWhereNodeFactory.createOrNode();
			whereNode.addChild(orNode);
			for (String propertyName : params.getSearchProperties()) {
				PropertyHQLGenerator generator = PropertyHQLGeneratorFactory
						.getGenerator(params.getItemClass(), instance, propertyName, resolverMgr, false,
								false);
				orNode.addChild(HQLWhereNodeFactory
						.createParametrizedConditionNode(generator
								.getHQLFragment(), generator
								.getValueFromText(params.getSearchText())));
			}
		}

		private void buildWhereDepartment() {
			whereNode.addChild(HQLWhereNodeFactory.createParametrizedConditionNode(IN_DEPARTMENT_SUBQUERY, department.getId()));
			switch (params.getQueryActive()) {
			case ACTIVE:
				whereNode.addChild(HQLWhereNodeFactory
						.createParametrizedConditionNode(IN_ACTIVE_SUBQUERY, Boolean.TRUE));
				break;
			case INACTIVE:
				whereNode.addChild(HQLWhereNodeFactory
						.createParametrizedConditionNode(IN_ACTIVE_SUBQUERY, Boolean.FALSE));
				break;
			default:
				// do nothing
			}
		}
		
		private boolean hasSearchText() {
			return (params.getSearchText() != null
					&& !params.getSearchText().isEmpty()
					&& params.getSearchProperties() != null
					&& params.getSearchProperties().length > 0);
		}
		
		private boolean hasFilter() {
			return (params.getFilterPropertyName() != null
					&& !params.getFilterPropertyName().isEmpty()
					&& params.getFilterPropertyValue() != null
					&& !params.getFilterPropertyValue().isEmpty()
					&& params.getFilterPropertyType() != null);
		}

		private String getObjectName() {
			String objectName = params.getItemClass().getSimpleName();
			if (objectName == null) {
				logger.debug(String.format("Could not get a class name for the item kind %s. Is it registered?", params.getItemClass()));
				return null;
			}
			return objectName;
		}


}
